/*
 * ====================================================================
 *
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */

package com.bah.tract.client.workstore.tol;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;

import javax.inject.Inject;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.bah.tract.client.ErrorHandler;
import com.bah.tract.security.PasswordEncryptor;

/**
 * A example that demonstrates how HttpClient APIs can be used to perform
 * form-based logon.
 */
public class TOLClientImpl implements TOLClient {

	public static final String TOL_PASS_PROP = "tolPassword";

	public static final String TOL_ID_PROP = "tolId";

	private String mUserId;

	private String mPassword;

	private final PasswordEncryptor mPasswordEncryptor;

	private static final Logger LOG = LoggerFactory
			.getLogger(TOLClientImpl.class);

	@Inject
	TOLClientImpl(final Preferences preferences,
			final PasswordEncryptor encryptor) {
		mPasswordEncryptor = encryptor;
		final String encryptedPass = preferences.get(TOL_PASS_PROP, null);
		if (encryptedPass != null) {
			try {
				mPassword = mPasswordEncryptor.decrypt(encryptedPass);
			} catch (final GeneralSecurityException e) {
				ErrorHandler.handleError(e);
			}
		}
		mUserId = preferences.get(TOL_ID_PROP, null);
		preferences.addPreferenceChangeListener(new PreferenceChangeListener() {

			@Override
			public final void preferenceChange(final PreferenceChangeEvent evt) {
				if (TOL_ID_PROP.equals(evt.getKey())) {
					mUserId = evt.getNewValue();
				} else if (TOL_PASS_PROP.equals(evt.getKey())) {
					try {
						mPassword = mPasswordEncryptor.decrypt(evt
								.getNewValue());
					} catch (final GeneralSecurityException e) {
						ErrorHandler.handleError(e);
					}
				}
			}
		});
	}

	@SuppressWarnings("deprecation")
	private DefaultHttpClient wrapClient(final HttpClient base) {

		try {

			final SSLContext ctx = SSLContext.getInstance("TLS");

			final X509TrustManager tm = new X509TrustManager() {

				@Override
				public void checkClientTrusted(final X509Certificate[] xcs,
						final String string) {

				}

				@Override
				public void checkServerTrusted(final X509Certificate[] xcs,
						final String string) {

				}

				@Override
				public X509Certificate[] getAcceptedIssuers() {

					return null;

				}

			};

			ctx.init(null, new TrustManager[] { tm }, null);

			final SSLSocketFactory ssf = new SSLSocketFactory(ctx);

			ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

			final ClientConnectionManager ccm = base.getConnectionManager();

			final SchemeRegistry sr = ccm.getSchemeRegistry();

			sr.register(new Scheme("https", ssf, 443));

			return new DefaultHttpClient(ccm, base.getParams());

		} catch (final Exception ex) {

			ex.printStackTrace();

			return null;

		}

	}

	@Override
	public void logWork(final Set<TOLWork> daysWork) throws IOException {
		DefaultHttpClient httpclient = new DefaultHttpClient();
		httpclient = wrapClient(httpclient);
		try {
			HttpGet httpget = new HttpGet("https://tolmobile.bah.com");

			HttpResponse response = httpclient.execute(httpget);
			HttpEntity entity = response.getEntity();

			LOG.debug("Login form get: {}", response.getStatusLine());
			EntityUtils.consume(entity);

			LOG.debug("Initial set of cookies:");
			final List<Cookie> cookies = httpclient.getCookieStore()
					.getCookies();
			if (cookies.isEmpty()) {
				LOG.debug("None");
			} else {
				for (int i = 0; i < cookies.size(); i++) {
					LOG.debug("- {}", cookies.get(i).toString());
				}
			}

			HttpPost httpost = new HttpPost(
					"https://tolmobile.bah.com/security/login.cfm");

			List<NameValuePair> nvps = new ArrayList<NameValuePair>();
			nvps.add(new BasicNameValuePair("Logon_Name", mUserId));
			nvps.add(new BasicNameValuePair("logon_Password", mPassword));

			httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

			response = httpclient.execute(httpost);
			entity = response.getEntity();
			final String location = response.getHeaders("Location")[0]
					.getValue().substring(3);
			EntityUtils.consume(entity);

			httpget = new HttpGet("https://tolmobile.bah.com/" + location);
			response = httpclient.execute(httpget);
			entity = response.getEntity();
			final ByteArrayOutputStream os = new ByteArrayOutputStream();
			IOUtils.copy(entity.getContent(), os);
			final TOLData tolData = new TOLResponseParser()
					.parseResponse(new ByteArrayInputStream(os.toByteArray()));
			final String laborDate = new SimpleDateFormat("MM/dd/yyyy")
					.format(new Date());
			for (final TOLWork work : daysWork) {
				httpost = new HttpPost(
						"https://tolmobile.bah.com/time_entry_a.cfm");

				nvps = new ArrayList<NameValuePair>();
				nvps.add(new BasicNameValuePair("timecard_id", tolData
						.getTimecardId()));
				nvps.add(new BasicNameValuePair("labor_id", ""));
				nvps.add(new BasicNameValuePair("time_period_code", tolData
						.getTimePeriodCode()));
				nvps.add(new BasicNameValuePair("labor_dt", laborDate));
				nvps.add(new BasicNameValuePair("charge_number", work
						.getNumber()));
				nvps.add(new BasicNameValuePair("tax_code", work.getTaxCode()));
				final String telecom = work.isTeleWork() ? "Y" : "N";
				nvps.add(new BasicNameValuePair("telecommute_flag", telecom));
				nvps.add(new BasicNameValuePair("reg_hrs", Double.toString(work
						.getMinutes() / 15.0)));
				nvps.add(new BasicNameValuePair("add_more_labor.x", "79"));
				nvps.add(new BasicNameValuePair("add_more_labor.y", "9"));

				httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

				response = httpclient.execute(httpost);
				entity = response.getEntity();
				EntityUtils.consume(entity);
			}

		} finally {
			// When HttpClient instance is no longer needed,
			// shut down the connection manager to ensure
			// immediate deallocation of all system resources
			httpclient.getConnectionManager().shutdown();
		}
	}

	@Override
	public void requestForm(final OutputStream os) throws IOException {
		DefaultHttpClient httpclient = new DefaultHttpClient();
		httpclient = wrapClient(httpclient);
		try {
			HttpGet httpget = new HttpGet("https://tolmobile.bah.com");

			HttpResponse response = httpclient.execute(httpget);
			HttpEntity entity = response.getEntity();

			System.out.println("Login form get: " + response.getStatusLine());
			EntityUtils.consume(entity);

			System.out.println("Initial set of cookies:");
			final List<Cookie> cookies = httpclient.getCookieStore()
					.getCookies();
			if (cookies.isEmpty()) {
				System.out.println("None");
			} else {
				for (int i = 0; i < cookies.size(); i++) {
					System.out.println("- " + cookies.get(i).toString());
				}
			}

			final HttpPost httpost = new HttpPost(
					"https://tolmobile.bah.com/security/login.cfm");

			final List<NameValuePair> nvps = new ArrayList<NameValuePair>();
			nvps.add(new BasicNameValuePair("Logon_Name", mUserId));
			nvps.add(new BasicNameValuePair("logon_Password", mPassword));

			httpost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

			response = httpclient.execute(httpost);
			entity = response.getEntity();
			final String location = response.getHeaders("Location")[0]
					.getValue().substring(3);
			EntityUtils.consume(entity);

			httpget = new HttpGet("https://tolmobile.bah.com/" + location);
			response = httpclient.execute(httpget);
			entity = response.getEntity();
			IOUtils.copy(entity.getContent(), os);
			os.close();

		} finally {
			// When HttpClient instance is no longer needed,
			// shut down the connection manager to ensure
			// immediate deallocation of all system resources
			httpclient.getConnectionManager().shutdown();
		}
	}
}
